% Copyright (C) 2000, 2001 Aladdin Enterprises.  All rights reserved.
% 
% This software is provided AS-IS with no warranty, either express or
% implied.
% 
% This software is distributed under license and may not be copied,
% modified or distributed except as expressly authorized under the terms
% of the license contained in the file LICENSE in this distribution.
% 
% For more information about licensing, please refer to
% http://www.ghostscript.com/licensing/. For information on
% commercial licensing, go to http://www.artifex.com/licensing/ or
% contact Artifex Software, Inc., 101 Lucas Valley Road #110,
% San Rafael, CA  94903, U.S.A., +1(415)492-9861.

% $Id: dumphint.ps,v 1.2 2004/04/08 16:18:25 giles Exp $
% Linearized PDF hint formatting utility.

%
% Linearized PDF hints generated by Acrobat suite seem to deviate from
% the published specification.
%
% /P (page offset hint table) key in hint stream is not generated by
% Adobe products. The key is no longer required in PDF 1.5.
%
% Per-page items 4 and 5 of the page offset hint table start from 1st
% page rather than 2nd page as the spec claims.
%
% All array entries start from the new byte boundary.
%

/table_width 79 def 
/col1_width  66 def

% Skip bits to the next byte boundary
/bytealign {		% <stream> bytealign -
  begin /N 0 def /B 0 def end
} bind def

% Set bit stream position and align it to byte boundary
/set_align {            % <<>> pos set_align -
  exch begin
  S exch setfileposition
  /N 0 def /B 0 def
  end
} bind def

% Read requested number of bits from the bit stream.
/bitread {		% <bstream> <width> bitwrite <value>
  exch begin
  0                     %  bit val
  { 
    1 index N .min      %  bit val m
    dup 3 1 roll        %  bit m val m 
    bitshift            %  bit m val<<m
    B 2 index N sub     %  bit m val<<m B m-N
    bitshift add        %  bit m val<<m+B>>(N-m)
    3 -1 roll           %  m val' bit 
    2 index sub         %  m val' bit'
    3 -1 roll           %  val' bit' m
    N exch sub dup      %  val' bit' N' N'
    1 exch bitshift     %  val' bit' N' 1<<N'
    1 sub B and         %  val' bit' N' B&(1<<N')
    /B exch def         %  val' bit' N'
    /N exch def         %  val' bit'
    dup 0 le {
      pop exit          %  val'
    } if
    /N N 8 add def
    /B B 8 bitshift S read not { 0 ( *** EOF! *** ) = } if add def
    exch
  } loop                %  bit' val'
  end
} bind def

% Print a string several times
/multiprint {           % cnt (s) multiprint -
  exch { dup print } repeat pop
} bind def

% Split the line into 2 substrings.
/split_line {           % (s) split_line () ()
  dup length col1_width gt {
    col1_width 1 sub
    dup -1 0 {
      dup               % (s) w i i
      3 index           % (s) w i i ()
      exch get          % (s) w i c
      32 eq {
        exch pop exit
      } if
      pop
    } for
    1 add               % (s) w'
    1 index exch        % (s) (s) w'
    0 exch              % (s) (s) 0 w'
    getinterval         % (s) (v)
  } {
    dup                 % (s) (s)
  } ifelse                  
  (\n) search {
    4 1 roll            % (pre) (s) (post) (match)
    pop pop             % (pre) (s)
    1 index length      % (pre) (s) len
    1 add               % (pre) (s) len+1
  } {
    exch                % (pre) (s)
    1 index length      % (pre) (s) len
  } ifelse
  1 index length        % (pre) (s) len Len
  1 index sub           % (pre) (s) len Len-len
  getinterval           % (pre) (post)
} bind def

% Print a 2 column table. The string is printed in 1st column
% left-aligned. The number is printed in 2nd column right-aligned.
/two_column {           % n () two_column -
  split_line            % n (a) ()
  3 1 roll              % () n (a)
  dup length            % () n (a) len
  exch print            % () n len
  exch =string cvs      % () len (n)
  dup length            % () len (n) len2
  3 -1 roll add         % () (n) len+len2 
  //table_width         % () (n) len+len2 78
  exch sub              % () (n) 78-len+len2 
  ( ) multiprint        % () (n)
  =                     % ()
  { dup length 0 eq { exit } if
    split_line exch =
  } loop
  pop
  () =
} bind def

% Print the header of a hint table
/table_header {               % () table_header -
  dup length dup
  table_width exch sub 2 idiv % () len sp
  dup ( ) multiprint          % () len sp
  3 -1 roll =                 % len sp
  ( ) multiprint              % len
  (=) multiprint
  ()= ()=
} bind def

% Pretty-print an array on top level
/dump_array {               % [ ] dump_array -
  ([) = ( ) print
  1 exch {
    =string cvs         % pos ()
    dup length dup      % pos () len len
    3 index add 1 add   % pos () len len+pos+1
    table_width gt {
      () =
      (  ) print        % pos () len
      2 add             % pos () pos'
      3 1 roll print pop % pos'
    } {
      ( ) print         % pos () len
      exch print        % pos len
      add 1 add         % pos'
    } ifelse
  } forall
  pop
  () = (]) =
} bind def

% Pretty-print an array on 2nd level
/dump_array2 {             % [ ] dump_array2 -
  (  [) print
  3 exch {
    =string cvs            % pos ()
    dup length dup         % pos () len len
    3 index add 1 add      % pos () len len+pos+1
    table_width 2 sub gt {
      () =
      (    ) print         % pos () len
      4 add                % pos () pos'
      3 1 roll print pop   % pos'
    } {
      ( ) print            % pos () len
      exch print           % pos len
      add 1 add            % pos'
    } ifelse
  } forall
  pop
  ( ]) =
} bind def

% Print an array header
/array_header {
  () = =
} bind def

% Analyze the page offset hint table.
/dump_page_offset_table {  % - dump_page_offset_table -
  hint_stream
    
  dup 32 bitread
  dup /hint_minnop exch def
  (1. The least number of objects in a page.) two_column

  dup 32 bitread
  dup /hint_1st_obj exch def
  (2. Location of the first page's page object.) two_column

  dup 16 bitread
  dup /hint_maxnopbits exch def
  (3. Bits for difference between max and min number of page objects.) two_column

  dup 32 bitread
  dup /hint_minpl exch def
  (4. Least length of a page.) two_column

  dup 16 bitread
  dup /hint_maxplbits exch def
  (5. Bits for difference between max and min length of a page.) two_column

  dup 32 bitread
  dup /hint_minsco exch def
  (6. Least start of Contents offset. ) 
  1 index 0 ne { (\n*** Acrobat expects 0 ***) concatstrings } if
  two_column

  dup 16 bitread
  dup /hint_maxscobits exch def
  (7. Bits for difference between max and min offset to the start of the content stream.) two_column

  dup 32 bitread
  dup /hint_mincl exch def
  (8. Least contents length.) two_column

  dup 16 bitread
  dup /hint_maxclbits exch def
  (9. Bits needed to represent the greatest Contents length.) two_column

  dup 16 bitread
  dup /hint_maxsorbits exch def
  (10. Bits needed to represent the greatest number of Shared Object references.) two_column

  dup 16 bitread
  dup /hint_sobits exch def
  (11. Bits needed to identify a Shared Object.) two_column

  dup 16 bitread
  dup /hint_numfbits exch def
  (12. Bits needed to represent numerator of fraction.) two_column

  dup 16 bitread
  dup /hint_denf exch def
  (13. Denominator of fraction.) two_column

  pop 

  LinearizationParams /N get

  % 1. Number of objects in the page.
  hint_stream bytealign
  /hint_page_obj [
    2 index {
      hint_stream hint_maxnopbits bitread
      hint_minnop add
    } repeat
  ] readonly def

  (1. Number of objects on the page) array_header
  hint_page_obj dump_array

  % 2, Page length in bytes.
  hint_stream bytealign
  /hint_page_len [
    2 index {
      hint_stream hint_maxplbits bitread
      hint_minpl add
    } repeat
  ] readonly def

  (2. Page length in bytes.) array_header
  hint_page_len dump_array

  % 3, Number of shared objects referenced from the page
  hint_stream bytealign
  /hint_page_sobj [
    2 index {
      hint_stream hint_maxsorbits bitread
    } repeat
  ] readonly def

  (3. Number of shared objects referenced from the page.) array_header
  hint_page_sobj dump_array

  % 4. Index into the shared objects hint table
  hint_stream bytealign
  /hint_page_sobj_id [
    0 1 4 index 1 sub {
      hint_page_sobj exch get [
        exch {
          hint_stream hint_sobits bitread
        } repeat
      ] readonly
    } for
  ] readonly def

  (4. Index into the shared objects hint table.) array_header
  ([) =
  hint_page_sobj_id { dump_array2 } forall
  (])=

  % 5. Fractional position for each shared object reference
  hint_stream bytealign
  /hint_page_sobj_pos [
    0 1 4 index 1 sub {
      hint_page_sobj exch get [
        exch {
          hint_stream hint_numfbits bitread
          hint_denf div
        } repeat
      ] readonly
    } for
  ] readonly def

  (5. Fractional position for each shared object reference. ) array_header
  ([)=
  hint_page_sobj_pos { dump_array2 } forall
  (])=

  % 6. Offset of the page's content stream from the beginning of the page.
  hint_stream bytealign
  /hint_page_content_offset [
    2 index {
      hint_stream hint_maxscobits bitread
      hint_minsco add
    } repeat
  ] readonly def

  (6. Offset of the page's content stream from the beginning of the page.) array_header
  hint_page_content_offset dump_array

  % 7. Length of the page's content stream in bytes.
  hint_stream bytealign
  /hint_page_content_len [
    2 index {
      hint_stream hint_maxclbits bitread
      hint_mincl add
    } repeat
  ] readonly def

  (7. Length of the page's content stream in bytes.) array_header
  hint_page_content_len dump_array

  pop
} bind def

% Analyze tha shared object hint table
/dump_shared_object_table { 
  hint_stream
    
  dup 32 bitread
  dup /shint_1st_obj_id exch def
  (1. Object number of the first object in the shared objects section) two_column

  dup 32 bitread
  dup /shint_1st_obj_pos exch def
  (2. Location of the first object in the shared objects section.) two_column

  dup 32 bitread
  dup /shint_1st_shared exch def
  (3. The number of shared object entries for the first page.) two_column

  dup 32 bitread
  dup /shint_all_shared exch def
  (4. Number of shared object entries for the shared objects section including 1st page.) two_column

  dup 16 bitread
  dup /shint_group_bits exch def
  (5. Number of bits needed to represent the greatest number of objects in a shared object group.) two_column

  dup 32 bitread
  dup /shint_group_least_sz exch def
  (6. Least length of a shared object group in bytes.) two_column

  dup 16 bitread
  dup /shint_group_diff_bits exch def
  (7. Bits for the difference between the greatest and least length of a shared object group size.)  two_column
  pop

  (1. length of the object group in bytes.) array_header
  hint_stream bytealign
  /shint_group_sz [
    shint_all_shared {
      hint_stream shint_group_diff_bits bitread
      shint_group_least_sz add
    } repeat
  ] readonly def

  shint_group_sz dump_array

  (2. MD5 signature flag) array_header
    hint_stream bytealign
    /shint_md5_flags
    [ shint_all_shared 
      { hint_stream 1 bitread
      } repeat
    ] readonly def

    shint_md5_flags dump_array

    (3. MD5 signature string) array_header
    false shint_md5_flags { 0 ne or } forall { 
      shint_md5_flags {
        0 eq {
          (<>)=
        } {
          hint_stream /S get 128 string
          readstring pop
          dup length 128 eq {
            ==
          } { pop (Error reading nd5 string.) ==
          } ifelse
        } ifelse
      } forall   
    } {
      () = (none) =
    } ifelse

    (4. The number of objects in the group.) array_header
    hint_stream bytealign
    /shint_group_cnt [
      shint_all_shared {
        hint_stream shint_group_bits bitread
      } repeat
    ] readonly def

    shint_group_cnt dump_array

  } bind def

% Analyze the thumbnail hint table.
/dump_thumbnail_table {
  hint_stream

  dup 32 bitread
  dup /thint_1st_obj_id exch def
  (1. Object number of the first thumbnail image.) two_column

  dup 32 bitread
  dup /thint_1st_obj_pos exch def
  (2. Location of the first thumbnail image.) two_column

  dup 32 bitread
  dup /thint_page_cnt exch def
  (3. Number of pages that have thumbnail images.) two_column

  dup 16 bitread
  dup /thint_no_thumbnail_bits exch def
  (4. Bits for the max number of consecutive pages without a thumbnail image.) two_column

  dup 32 bitread
  dup /thint_min_sz exch def
  (5. The least length of a thumbnail image in bytes.) two_column

  dup 15 bitread
  dup /thint_obj_sz_bits exch def
  (6. Bits for the difference between max and min length of a thumbnail image.) two_column

  dup 32 bitread
  dup /thint_min_obj_cnt exch def
  (7. The least number of objects in a thumbnail image.) two_column

  dup 16 bitread
  dup /thint_obj_cnt_bits exch def
  (8. Bits for the difference between max and min number of objects in a thumbnail image.) two_column

  dup 32 bitread
  dup /thint_1st_shared_obj exch def
  (9. First object in the thumbnail shared objects section.) two_column

  dup 32 bitread
  dup /thint_1st_shared_pos exch def
  (10. Location of the first object in the thumbnail shared objects section.) two_column

  dup 32 bitread
  dup /thint_shared_cnt exch def
  (11. Number of thumbnail shared objects.) two_column

  dup 32 bitread
  dup /thint_shared_section_sz exch def
  (12. Length of the thumbnail shared objects section in bytes.) two_column

  pop
  LinearizationParams /N get

  (1. The number of preceding pages lacking a thumbnail image.) array_header
  hint_stream bytealign
  /thint_no_thumbnail_pages [
    2 index {
      hint_stream thint_no_thumbnail_bits bitread
    } repeat
  ] readonly def

  thint_no_thumbnail_pages dump_array

  (2. Number of objects in this page's thumbnail image.) array_header
  hint_stream bytealign
  /thint_page_obj_cnt [
    2 index {
      hint_stream thint_obj_cnt_bits bitread
      thint_min_obj_cnt add
    } repeat
  ] readonly def

  thint_page_obj_cnt dump_array

  (3. Length of this page's thumbnail image in bytes.) array_header
  hint_stream bytealign
  /thint_page_obj_sz [
    2 index {
      hint_stream thint_obj_sz_bits bitread
      thint_min_sz add
    } repeat
  ] readonly def

  thint_page_obj_sz dump_array
  pop
} bind def

% Analyze the generic hint table.
% The hint field names are re-used. 
/dump_generic_table { 
  hint_stream

  dup 32 bitread
  dup /ghint_1st_obj exch def
  (1. Object number of the first object in the group.) two_column

  dup 32 bitread
  dup /ghint_1st_obj_pos exch def
  (2. Location of the first object in the group.) two_column

  dup 32 bitread
  dup /ghint_obj_cnt exch def
  (3. Number of objects in the group.) two_column

  dup 32 bitread
  dup /ghint_group_sz exch def
  (4. Length of the object group in bytes.) two_column

  pop
} bind def

% Analyze the interactive hint table.
% The hint field names are re-used.
/dump_interactive_table {
  hint_stream

  dup 32 bitread
  dup /ihint_1st_obj exch def
  (1. Object number of the first object in the group.) two_column

  dup 32 bitread
  dup /ihint_1st_obj_pos exch def
  (2. Location of the first object in the group.) two_column

  dup 32 bitread
  dup /ihint_obj_cnt exch def
  (3. Number of objects in the group.) two_column

  dup 32 bitread
  dup /ihint_group_sz exch def
  (4. Length of the object group in bytes.) two_column

  dup 32 bitread
  dup /ihint_shared_cnt exch def
  (5. Number of shared object references.) two_column

  dup 16 bitread
  dup /ihint_shared_obj_bits exch def
  (6. Bits for the max shared object id used by the interactive form or the logical structure hierarchy.)
  1 index hint_sobits ne {
    (\n*** This fiels is not equal to max shared object ID bits ***) concatstrings 
  } if

  pop

  (7. Shared object identifier.) array_header
  hint_stream bytealign
  /ihint_shared_obj_id [
    ihint_shared_cnt {
      hint_stream hint_sobits bitread
    } repeat
  ] readonly def

  ihint_shared_obj_id dump_array
} bind def

% Enumerate all documented hint tables.
/dump_all_tables {  % <<stream>> dump_all_tables -
  (Page offset hint table) table_header
  hint_stream 0 set_align
  dump_page_offset_table

  (S, Shared object hint table) table_header
  dup /S .knownget {
    hint_stream exch set_align
    dump_shared_object_table
  } {
    (Required table is not found.) error_msg
  } ifelse

  dup /T .knownget {
    (T, Thumbnail hint table) table_header
    hint_stream exch set_align
    dump_thumbnail_table
  } if   

  dup /O .knownget {
    (O, Outline hint table) table_header
    hint_stream exch set_align
    dump_generic_table
  } if

  dup /A .knownget {
    (A, Thread information hint table) table_header 
    hint_stream exch set_align
    dump_generic_table
  } if

  dup /E .knownget {
    (E, Named destination hint table) table_header
    hint_stream exch set_align
    dump_generic_table
  } if

  dup /V .knownget {
    (V, Interactive form hint table) table_header
    hint_stream exch set_align
    dump_interactive_table
  } if

  dup /I .knownget {
    (I, Information dictionary hint table) table_header
    hint_stream exch set_align
    dump_generic_table
  } if

  dup /C .knownget {
    (C, Logical structure hint table) table_header
    hint_stream exch set_align
    dump_interactive_table
  } if

  dup /L .knownget {
    (L, Page label hint table) table_header
    hint_stream exch set_align
    dump_generic_table
  } if

    pop
} bind def


% Load PDF file and extract the hint stream.
/pdf_dump_hints {                             % <infile> pdf_dump_hints -
  dup (r) file                                % fname
  false exch                                  % fname F file 
  {
    dup 7 string readstring pop               % fname F file ()
    (%PDF-1.) ne { pop exit } if              % fname F file
    dup 0 setfileposition
    dup token not { pop exit } if             % fname F file obj
    dup type /integertype ne { pop exit } if  % fname F file obj
    1 index token not { pop pop exit } if     % fname F file obj gen
    dup type /integertype ne {pop pop exit}if % fname F file obj gen
    4 2 roll                                  % fname obj gen F file
    dup 0 setfileposition
    exch true or                              % fname obj gen file T
    exit
  } loop
  {
    pdfdict begin
    pdfopenfile dup begin
    40 dict begin
    /IDict exch def
    .setsafe

    % Read all objects into memory.
    Trailer touch
    resolveR                                  % fname <<>>
    dup /Linearized known {
      dup /L get                              % fname <<>> Len
      3 -1 roll status not { 0 0 0 0 } if     % <<>> Len
      pop pop exch pop                        % <<>> Len len
      eq {
        /LinearizationParams exch def
        LinearizationParams /H get
        dup length 2 eq {
          0 get PDFoffset add PDFfile exch setfileposition
          PDFfile token pop 
          PDFfile token pop
          resolveR dup true resolvestream
          /ReusableStreamDecode filter 
          bitstream dup bytealign
          /hint_stream exch def

          dump_all_tables
        } {
          pop
          (Overflow hint stream is not supported.) =
        } ifelse
      } {
        pop
        (Wrong file length in linearization dictionary.) =
      } ifelse
    } {
      pop (The file is not linearized.) =
    } ifelse
    end		% temporary dict
    end		% IDict
    end
  } {
    pop
    (Input file is not a valid PDF file.) =
  } ifelse
} bind def

% Initial setup
/dump_hints {
  counttomark 1 eq {
    exch pop
    save exch
    3000000 setvmthreshold
    pdfoptdict begin 
    pdf_dump_hints
    end
    restore
    true
  } {
    cleartomark false
  } ifelse
} bind def

/shellarguments {false} def
(pdfopt.ps) runlibfile
currentdict /shellarguments undef

% Check for command line arguments.
mark shellarguments {
  dump_hints not {
    (Usage: gs -dNODISPLAY  -- dumphint.ps input.pdf) = flush
  } if
} {
  pop
} ifelse

% EOF
